
import socket
import time
import json
import threading
import struct
import time
import random
import crcmod
import os
import math
import datetime
import logging

crc16 = crcmod.predefined.mkPredefinedCrcFun("xmodem")
pic_body_len = 2048

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.NOTSET, format="%(asctime)s - %(levelname)s - %(message)s")


class bcd():
    def __init__(self):
        pass

    def cover_hex_str_to_bcd(self, hex_str: str):
        if len(hex_str) != 12:
            raise Exception("len not enough")
        year = hex_str[:2]
        month = hex_str[2:4]
        day = hex_str[4:6]
        hour = hex_str[6:8]
        minute = hex_str[8:10]
        sencod = hex_str[10:]

        c = self._cover_int_to_bcd

        return c(year) + c(month) + c(day) + c(hour) + c(minute) + c(sencod)

    def _cover_int_to_bcd(self, num: int):
        num_int = int(num)
        num_hex_int = (math.floor((num_int / 10)) << 4) + (num_int % 10)
        return int(num_hex_int).to_bytes(1, byteorder='little', signed=True)


class laneServerSimulator():
    def __init__(self, host, port):
        self.init_servet(host, port)
        self.init_qx_server(host)

    def init_servet(self, host, port):
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        self.server.settimeout(72000000)
        self.server.bind((host, port))        # 绑定端口

        self.bcd_cover = bcd()

        self.host = host
        self.port = port

        self.head_len = 34
        self.client = ''

        self.pic_recv_struct = {}
        # self.bytesss = b''
        self.chedaoji_no_g = b''
        self.file_code = 0

        self.start_auto_calibration_flag = False

    def init_qx_server(self, host):
        self.qx_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.qx_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.qx_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        self.qx_server.settimeout(72000000)
        self.qx_server.bind((host, 8002))
        self.qx_client = ''

    def start_qx_server(self):
        while True:
            try:
                self.qx_server.listen(1000)                      # 监听
            except:
                self.init_qx_server(self.host)
                self.qx_server.listen(1000)                      # 监听

            self.qx_client, addr = self.qx_server.accept()          # 等待客户端连接
            logger.info("%s qx连接上了 ", addr)
            try:
                while True:
                    try:
                        data = self.do_recv_qx()
                    except Exception as e:
                        logger.error(e)
                        break
                    if not data:
                        logger.warning('qx数据为空，我要退出了')
                        break
                    if data == b'GET /RTCM32_GGB HTTP/1.0\r\nUser-Agent: NTRIP QX NTRIPClient/20131124\r\nAccept: */*\r\nConnection: close\r\nAuthorization: Basic cXh3b25lMDAzOjc2OWE1YzE=\r\n\r\n':
                        self.qx_client.send(b"ICY 200 OK")
                    else:
                        self.qx_client.send(b"$GNGGA,005517.00,2256.77897,N,11315.35324,E,5,12,0.58,5.0,M,-5.5,M,11.0,0901*41")
            finally:
                self.qx_server.close()

    def do_recv(self):
        try:
            data = self.client.recv(self.head_len)
            return data
        except Exception as e:
            raise Exception(e)
        if not data:
            logger.warning('数据为空，我要退出了')
            return False

    def do_recv_qx(self):
        try:
            data = self.qx_client.recv(1024)
            return data
        except Exception as e:
            raise Exception(e)
        if not data:
            logger.warning('qx数据为空，我要退出了')
            return False

    def do_recv_retain(self, num):
        try:
            data = self.client.recv(num)
            return data
        except Exception as e:
            raise Exception(e)
        if not data:
            logger.warning('数据为空，我要退出了')
            return False

    def start(self):
        while True:
            try:
                self.server.listen(1000)                      # 监听
            except:
                self.init_servet(self.host, self.port)
                self.server.listen(1000)                      # 监听

            file_map = {}
            self.client, addr = self.server.accept()          # 等待客户端连接
            logger.info(str(addr) + " 连接上了")
            try:
                while True:
                    try:
                        data = self.do_recv()
                    except Exception as e:
                        logger.error(e)
                        break
                    if not data:
                        logger.warning('数据为空，我要退出了')
                        break

                    head, park_id, frame_no, chedaoji_no, reverse, cmd, data_len = struct.unpack("!HIB12s12sBH", data)

                    try:
                        retain_data = self.do_recv_retain(data_len + 2)
                        while len(retain_data) != data_len+2:
                            retain_data += self.do_recv_retain(data_len + 2 - len(retain_data))

                    except Exception as e:
                        logger.error(e)
                        break
                    if not data:
                        logger.warning('数据为空，我要退出了')
                        break

                    _other, frame_crc = struct.unpack("!"+str(data_len)+"sH", retain_data)

                    self.chedaoji_no_g = chedaoji_no
                    now_time_str = datetime.datetime.strftime(datetime.datetime.now(), '%y%m%d%H%M%S')
                    now_time_bcd = self.bcd_cover.cover_hex_str_to_bcd(now_time_str)

                    # 分块crc校验
                    cal_frame_crc = crc16(data[2:] + retain_data[:-2])
                    if frame_crc != cal_frame_crc:
                        logger.error("帧crc校验不通过: " + str(hex(cmd)))
                        logger.error(str(data + retain_data))

                    if cmd == 0x71:
                        logger.info("收到注册信息")
                        pack = struct.pack("!HIB12s12sBH", head, park_id, frame_no, chedaoji_no, reverse, cmd, 15)
                        # 0x21 0x11 0x
                        pack += struct.pack("!B6s8s", 0x00, now_time_bcd, b'\x00\x00\x00\x00\x00\x00\x00\x00')
                        crc = crc16(pack[3:])
                        pack += struct.pack("!H", crc)
                        self.client.send(pack)

                    if cmd == 0x72:
                        time_bcd, device_run_arg, gps_data1, gps_data2, crc = struct.unpack("!6s30s80s80sH", retain_data)
                        logger.info("收到心跳信息；gps 数据1：" + gps_data1.decode('ascii').strip(b'\x00'.decode())+"；gps 数据2：" + gps_data2.decode('ascii').strip(b'\x00'.decode())+'\n')

                        pack = struct.pack("!HIB12s12sBH", head, park_id, frame_no, chedaoji_no, reverse, cmd, 10)
                        pack += struct.pack("!6s4s", now_time_bcd, b'0000')
                        crc = crc16(pack[3:])
                        pack += struct.pack("!H", crc)
                        self.client.send(pack)

                    if cmd == 0x74:
                        time_bcd, license_no, license_color, license_type, cam_id, confident, img_unique_tag, gps_type, gps_data1, gps_data2, crc = struct.unpack("!6s16sBBBBBB80s80sH", retain_data)
                        # time_bcd, license_no, license_color, license_type, cam_id, img_unique_tag, gps_type, gps_data, crc = struct.unpack("!6s16sBBBBB80sH", retain_data)
                        logger.info("车牌信息：相机时间："+str(time_bcd.hex())+"；车牌号："+str(license_no)+"；相机id："+str(cam_id) + "；车牌可信度："+str(confident)+"；车牌图片唯一标识："+str(img_unique_tag) + "; gps类型：" +
                                    str(gps_type) + "；gps 数据1：" + gps_data1.decode('ascii').strip(b'\x00'.decode())+"；gps 数据2：" + gps_data2.decode('ascii').strip(b'\x00'.decode())+'\n')
                        # logger.info("相机时间："+str(time_bcd.hex())+"；车牌号："+license_no.decode('GB2312').strip(b'\x00'.decode())+"；相机id："+str(cam_id)+"；车牌图片唯一标识："+str(img_unique_tag) + "; gps类型：" + str(gps_type) + "；gps 数据1：" + gps_data.decode('ascii').strip(b'\x00'.decode())+"；gps 数据1：" + '\n')
                        self.client.send(pack)

                    if cmd == 0x78:
                        time_bcd, gps_type, gps_data1, gps_data2, crc = struct.unpack("!6sB80s80sH", retain_data)
                        logger.info("gps时间："+str(time_bcd.hex())+"; gps类型：" + str(gps_type) + "；gps 数据1：" + gps_data1.decode('ascii').strip(b'\x00'.decode()) + "；gps 数据2：" + gps_data2.decode('ascii').strip(b'\x00'.decode())+'\n')
                        self.client.send(pack)

                    if cmd == 0x8f:
                        time_bcd, cam_id, img_unique_tag, gps_type, gps_data1, gps_data2, reverse, crc = struct.unpack("!6sBBB80s80sIH", retain_data)
                        logger.info("标定v2信息：相机时间："+str(time_bcd.hex())+"；相机id："+str(cam_id) + "；标定图片唯一标识："+str(img_unique_tag) + "; gps类型：" +
                                    str(gps_type) + "；gps 数据1：" + gps_data1.decode('ascii').strip(b'\x00'.decode())+"；gps 数据2：" + gps_data2.decode('ascii').strip(b'\x00'.decode())+'\n')

                    if cmd == 0x89:
                        response_code, file_code, file_block_type, crc = struct.unpack("!IHHH", retain_data)
                        logger.info("固件下发响应："+"响应码："+hex(response_code)+"；文件代码："+hex(file_code)+"；文件类别：" + hex(file_block_type))

                    if cmd == 0x90:
                        file_id, block_all_num, block_no, file_type, crc_all_file, last_bytes, pic_nums_and_cam_id, pic_time_bcd, body, crc = struct.unpack("!HHHHHHH6s"+str(pic_body_len)+"sH", retain_data)
                        pic_nums_and_cam_id_bytes = int(pic_nums_and_cam_id).to_bytes(2, byteorder='big', signed=False)
                        file_no, cam_id = struct.unpack("!bb", pic_nums_and_cam_id_bytes)

                        if block_no == 0:
                            file_type_str = "01" if file_type == 0x9998 else "00" if file_type == 0x9997 else "02"
                            file_name = ''.join(['%02X' % i for i in chedaoji_no])+''.join(['%02X' % i for i in pic_time_bcd])+''.join(['%02X' % i for i in pic_nums_and_cam_id_bytes])+file_type_str
                            first_body = struct.unpack("!" + str(pic_body_len) + "s", body)[0]
                            file_map.update({file_id: str(file_name)})
                            # with open(str(file_name)+'.png', 'ab') as wf:
                            #     ret = wf.write(first_body)
                            #     wf.flush()
                            self.pic_recv_struct.update({
                                str(file_id): {
                                    "bytes": first_body,
                                    "num": 1,
                                }
                            })
                            # logger.info("收到图片信息，文件名："+str(file_name) + "；分块号：" + str(block_no)+"；图片序号："+str(file_name[36:38])+"；摄像头Id："+str(file_name[38:40]))
                        elif block_no == (block_all_num - 1):
                            file_name = file_map.get(file_id)
                            # with open(file_name+'.png', 'ab') as wf:
                            #     ret = wf.write(body[:last_bytes])
                            #     wf.flush()
                            # self.bytesss += body[:last_bytes]
                            pic_struct = self.pic_recv_struct.get(str(file_id))
                            if pic_struct == None:
                                raise Exception("pic的map为空")
                            pic_struct['bytes'] += body[:last_bytes]
                            self.pic_recv_struct.update({str(file_id): pic_struct})
                            logger.info("收到图片信息，文件名："+str(file_name) + "；分块号：" + str(block_no)+"；总块数："+str(block_all_num)+"；图片序号："+str(file_name[36:38])+"；摄像头Id："+str(file_name[38:40]))
                        else:
                            file_name = file_map.get(file_id)
                            # with open(file_name+'.png', 'ab') as wf:
                            #     ret = wf.write(body)
                            #     wf.flush()
                            pic_struct = self.pic_recv_struct.get(str(file_id))
                            if pic_struct == None:
                                raise Exception("pic的map为空")
                            pic_struct['bytes'] += body
                            pic_struct['num'] += 1
                            self.pic_recv_struct.update({str(file_id): pic_struct})
                            # logger.info("收到图片信息，文件名："+str(file_name) + "；分块号：" + str(block_no)+"；总块数："+str(block_all_num)+"；图片序号："+str(file_name[36:38])+"；摄像头Id："+str(file_name[38:40]))

                        if block_no != block_all_num - 1:
                            pic_struct = self.pic_recv_struct.get(str(file_id))
                            if pic_struct == None:
                                raise Exception("pic的map为空")
                            # pic_num = pic_struct['num']
                            # if file_id % 5 != 0:
                            #     if pic_num == 5:
                            #         # 回复
                            #         pack = struct.pack("!HIB12s12sBH", head, park_id, frame_no, chedaoji_no, reverse, cmd, 6)
                            #         pack += struct.pack("!4sH", b'\x00\x00\x00\x01', file_id)
                            #         crc = crc16(pack[3:])
                            #         pack += struct.pack("!H", crc)
                            #         pic_struct = self.pic_recv_struct.get(str(file_id))
                            #         pic_struct['num'] = 0
                            #         self.pic_recv_struct.update({str(file_id): pic_struct})
                            #         logger.info("返回图片id: "+str(file_id)+" 的响应")
                            #         self.client.send(pack)
                        else:
                            file_name = file_map.get(file_id)
                            pack = struct.pack("!HIB12s12sBH", head, park_id, frame_no, chedaoji_no, reverse, cmd, 9)
                            pack += struct.pack("!6sbbb", pic_time_bcd, 0x1 if file_type == 0x9998 else 0x0 if file_type == 0x9997 else 0x02, cam_id, file_no)
                            crc = crc16(pack[3:])
                            pack += struct.pack("!H", crc)
                            logger.info(file_name + "响应发送")
                            self.client.send(pack)

                            pic_struct = self.pic_recv_struct.get(str(file_id))

                            if block_no == 0:
                                ccc = crc16(pic_struct['bytes'][:last_bytes])
                            else:
                                ccc = crc16(pic_struct['bytes'])
                            if ccc == crc_all_file:
                                logger.info("crc图片校验通过")
                            else:
                                logger.error("crc图片校验不通过")

                            self.pic_recv_struct.pop(str(file_id))

                            # with open(file_name+'.png', 'ab') as wf:
                            #     wf.write(pic_struct['bytes'])
                            #     wf.flush()
                            # self.bytesss = b''
            finally:
                self.server.close()                    # 关闭连接

    def send_calibration(self):
        time.sleep(3)
        while True:
            time.sleep(3)
            self.send_calibration_once()

    def send_calibration_once(self):
        if hasattr(self.client, 'send'):
            pack = struct.pack("!HIB12s12sBH", 0x555a, 1000480, 0, self.chedaoji_no_g, b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', 0x8e, 1)
            pack += struct.pack("!B", 0x01)
            crc = crc16(pack[3:])
            pack += struct.pack("!H", crc)
            logger.info("下发标定")
            self.client.send(pack)

    def send_start_calibration_v2(self):
        if hasattr(self.client, 'send'):
            pack = struct.pack("!HIB12s12sBH", 0x555a, 1000480, 0, self.chedaoji_no_g, b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', 0x8f, 1)
            pack += struct.pack("!B", 0x01)
            crc = crc16(pack[3:])
            pack += struct.pack("!H", crc)
            logger.info("下发开始标定v2")
            self.client.send(pack)

    def send_stop_calibration_v2(self):
        if hasattr(self.client, 'send'):
            pack = struct.pack("!HIB12s12sBH", 0x555a, 1000480, 0, self.chedaoji_no_g, b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', 0x8f, 1)
            pack += struct.pack("!B", 0x02)
            crc = crc16(pack[3:])
            pack += struct.pack("!H", crc)
            logger.info("下发停止标定v2")
            self.client.send(pack)

    def auto_calibration_start(self):
        def auto():
            while True:
                if self.start_auto_calibration_flag == False:
                    break
                self.send_start_calibration_v2()
                time.sleep(120)
                if self.start_auto_calibration_flag == False:
                    break
                self.send_stop_calibration_v2()
                time.sleep(120)

        t1 = threading.Thread(target=auto)
        t1.start()

    def send_firm_ware(self):
        with open("/home/vmuser/work/robot/pyServer/Tim_root.tar.gz", 'rb') as rf:
            import math
            rb = rf.read()
            b_len = len(rb)
            all_pack_nums = math.ceil(b_len/pic_body_len)
            last_bytes = b_len % pic_body_len
            file_code = self.file_code
            file_crc = crc16(rb)
            now_time_str = datetime.datetime.strftime(datetime.datetime.now(), '%y%m%d%H%M%S')
            now_time_bcd = self.bcd_cover.cover_hex_str_to_bcd(now_time_str)

            self.file_code += 1

            logger.info("下发固件包")
            for i in range(all_pack_nums):
                # logger.info("下发第"+str(i+1)+"个固件包；共"+str(all_pack_nums)+"个固件包；最后固件包大小"+str(last_bytes))
                pack = struct.pack("!HIB12s12sBH", 0x555a, 1000480, 0, self.chedaoji_no_g, b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', 0x89, pic_body_len + 2+2+2+2+6+6)
                pack += struct.pack("!HHH2sHH2s6s", file_code, all_pack_nums, i, b"\x99\x99", file_crc, last_bytes, b"\xff\xff", now_time_bcd)
                bytes_send = rb[i*pic_body_len:(i+1)*pic_body_len] if i != all_pack_nums-1 else rb[i*pic_body_len:i*pic_body_len+last_bytes]+bytes([0 for i in range(pic_body_len - last_bytes)])
                pack += struct.pack("!"+str(pic_body_len)+"s", bytes_send)
                crc = crc16(pack[3:])
                pack += struct.pack("!H", crc)
                if hasattr(self.client, 'send'):
                    self.client.send(pack)
                else:
                    break
            logger.info("固件包下发完毕")

    def get_user_input(self):
        while True:
            cmd = input()
            if cmd == "cs":
                self.start_auto_calibration_flag = False
                self.send_start_calibration_v2()
            if cmd == "css":
                self.start_auto_calibration_flag = False
                self.send_stop_calibration_v2()
            if cmd == "c":
                self.start_auto_calibration_flag = False
                self.send_calibration_once()
            if cmd == "ca":
                self.start_auto_calibration_flag = True
                self.auto_calibration_start()
            if cmd == "f":
                self.send_firm_ware()


if __name__ == '__main__':
    ser = laneServerSimulator("0.0.0.0", 10020)
    t1 = threading.Thread(target=ser.start)
    t2 = threading.Thread(target=ser.send_calibration)
    t3 = threading.Thread(target=ser.start_qx_server)
    inputFn = threading.Thread(target=ser.get_user_input)

    t1.start()
    inputFn.start()
    # t2.start()
    # t3.start()
